// APITEST: 一个简化版 Postman 风格请求工具
// 依赖：jQuery、Bootstrap、可选 Ace Editor（CDN）

(function(){
  let currentEndpointId = null;

  async function fetchApiList(){
    try { return await $.get('/api/apitest/endpoints'); } catch(e){ return []; }
  }

  async function fetchApiDetail(id){
    try { return await $.get(`/api/apitest/endpoints/${id}`); } catch(e){ return null; }
  }

  async function createApiEndpoint(payload){
    try { return await $.ajax({ url: '/api/apitest/endpoints', method: 'POST', contentType: 'application/json', data: JSON.stringify(payload) }); } catch(e){ throw e; }
  }

  async function updateApiEndpoint(id, payload){
    try { return await $.ajax({ url: `/api/apitest/endpoints/${id}`, method: 'PUT', contentType: 'application/json', data: JSON.stringify(payload) }); } catch(e){ throw e; }
  }

  async function deleteApiEndpoint(id){
    try { return await $.ajax({ url: `/api/apitest/endpoints/${id}`, method: 'DELETE' }); } catch(e){ throw e; }
  }

  function buildTree(list){
    const map = new Map();
    list.forEach(n => { n.children = []; map.set(n.id, n); });
    const roots = [];
    list.forEach(n => {
      if (n.parent_id) {
        const p = map.get(n.parent_id);
        if (p) p.children.push(n); else roots.push(n);
      } else {
        roots.push(n);
      }
    });
    return roots;
  }

  function renderTreeNodes(nodes, $container, level){
    nodes.sort((a,b)=> (a.order_index - b.order_index) || a.name.localeCompare(b.name));
    nodes.forEach(n => {
      const $row = $(`<div class="d-flex align-items-center py-1" data-id="${n.id}"></div>`);
      $row.append(`<span class="text-muted" style="padding-left:${level*14}px"></span>`);
      $row.append(`<i class="fa fa-circle me-2 ${n.method.toUpperCase()==='GET'?'text-success': 'text-primary'}"></i>`);
      $row.append(`<span class="badge bg-secondary me-2">${n.method}</span>`);
      $row.append(`<button class="btn btn-link p-0 text-start flex-grow-1 apitest-tree-item" data-id="${n.id}"><i class="fa fa-link me-1"></i>${$('<div>').text(n.name).html()}</button>`);
      $row.append(`<button class="btn btn-sm btn-outline-danger ms-2 apitest-tree-del" data-id="${n.id}"><i class="fa fa-trash"></i></button>`);
      $container.append($row);
      if (n.children && n.children.length) renderTreeNodes(n.children, $container, level+1);
    });
  }

  async function renderApiTree(filterText){
    const list = await fetchApiList();
    const $tree = $('#apitest-tree');
    $tree.empty();
    const filtered = filterText ? list.filter(it => String(it.name||'').toLowerCase().includes(String(filterText).toLowerCase())) : list;
    const roots = buildTree(filtered);
    renderTreeNodes(roots, $tree, 0);
  }

  function fillDetail(detail){
    if (!detail) return;
    currentEndpointId = detail.id;
    $('#api-name').val(detail.name||'');
    $('#api-method').val(detail.method||'GET');
    $('#api-url').val(detail.url||'');
    // headers
    $('#headers-rows').empty();
    const headersArr = detail.headers ? (function(){ try{ return JSON.parse(detail.headers); }catch(_){return [];} })() : [];
    headersArr.forEach(()=> $('#add-header-row').click());
    const $htrs = $('#headers-rows').find('tr');
    headersArr.forEach((h,i)=>{ const $tr = $htrs.eq(i); $tr.find('.kv-key').val(h.key||''); $tr.find('.kv-value').val(h.value||''); });
    // params
    $('#params-rows').empty();
    const paramsArr = detail.params ? (function(){ try{ return JSON.parse(detail.params); }catch(_){return [];} })() : [];
    paramsArr.forEach(()=> $('#add-param-row').click());
    const $ptrs = $('#params-rows').find('tr');
    paramsArr.forEach((p,i)=>{ const $tr = $ptrs.eq(i); $tr.find('.kv-key').val(p.key||''); $tr.find('.kv-value').val(p.value||''); $tr.find('.kv-type').val(p.type||'string'); });
    // body
    $('#body-type').val(detail.body_type||'none').trigger('change');
    $('#body-content-type').val(detail.content_type||'');
    const bodyType = detail.body_type||'none';
    if (bodyType === 'json') {
      const jsonStr = detail.body || '{\n  "hello": "world"\n}';
      if (window.ace && window.ace.edit) {
        const ed = window.ace.edit('apitest-json-editor');
        ed.session.setValue(jsonStr);
      } else {
        $('#apitest-json-textarea').val(jsonStr);
      }
    } else if (bodyType === 'x-www-form-urlencoded' || bodyType === 'form-data') {
      $('#body-kv-rows').empty();
      let kvArr = [];
      try{ kvArr = detail.body ? JSON.parse(detail.body) : []; } catch(_){ kvArr = []; }
      kvArr.forEach(()=> $('#add-body-kv-row').click());
      const $btrs = $('#body-kv-rows').find('tr');
      kvArr.forEach((b,i)=>{ const $tr = $btrs.eq(i); $tr.find('.kv-key').val(b.key||''); $tr.find('.kv-value').val(b.value||''); $tr.find('.kv-kind').val('text').trigger('change'); });
    } else if (bodyType === 'xml' || bodyType === 'raw') {
      $('#raw-body-text').val(detail.body||'');
    } else {
      // none/binary
      $('#raw-body-text').val('');
      $('#body-kv-rows').empty();
    }
  }

  function collectDefinition(){
    const name = $('#api-name').val().trim();
    const method = $('#api-method').val();
    const url = $('#api-url').val().trim();
    const headersArr = collectRows($('#headers-rows'), 'headers');
    const paramsArr = collectRows($('#params-rows'), 'params');
    const bodyType = $('#body-type').val();
    const contentTypeInput = $('#body-content-type').val().trim();
    let bodyStr = null;
    if (bodyType === 'json') {
      const jsonStr = (window.ace && window.ace.edit) ? window.ace.edit('apitest-json-editor').getValue() : $('#apitest-json-textarea').val();
      bodyStr = jsonStr || '';
    } else if (bodyType === 'xml' || bodyType === 'raw') {
      bodyStr = $('#raw-body-text').val() || '';
    } else if (bodyType === 'x-www-form-urlencoded' || bodyType === 'form-data') {
      const kv = collectRows($('#body-kv-rows'), 'body');
      const textOnly = kv.filter(it => it.kind === 'text').map(it => ({ key: it.key, value: it.value||'' }));
      bodyStr = JSON.stringify(textOnly);
    } else {
      bodyStr = null;
    }
    return {
      name, method, url,
      headers: JSON.stringify(headersArr),
      params: JSON.stringify(paramsArr),
      body_type: bodyType,
      body: bodyStr,
      content_type: contentTypeInput,
      parent_id: null,
      order_index: 0
    };
  }
  function kvRowTemplate(kind){
    // kind: 'params' | 'headers' | 'cookies' | 'body'
    if (kind === 'params') {
      return `
        <tr>
          <td><input class="form-control form-control-sm kv-key" placeholder="key"></td>
          <td><input class="form-control form-control-sm kv-value" placeholder="value"></td>
          <td>
            <select class="form-select form-select-sm kv-type">
              <option value="string">string</option>
              <option value="integer">integer</option>
              <option value="boolean">boolean</option>
              <option value="number">number</option>
              <option value="array">array</option>
            </select>
          </td>
          <td><button class="btn btn-sm btn-outline-danger kv-remove"><i class="fa fa-trash"></i></button></td>
        </tr>`;
    }
    if (kind === 'headers') {
      return `
        <tr>
          <td><input class="form-control form-control-sm kv-key" placeholder="Header"></td>
          <td><input class="form-control form-control-sm kv-value" placeholder="Value"></td>
          <td><button class="btn btn-sm btn-outline-danger kv-remove"><i class="fa fa-trash"></i></button></td>
        </tr>`;
    }
    if (kind === 'cookies') {
      return `
        <tr>
          <td><input class="form-control form-control-sm kv-key" placeholder="Name"></td>
          <td><input class="form-control form-control-sm kv-value" placeholder="Value"></td>
          <td><input class="form-control form-control-sm kv-expire" type="number" min="1" placeholder="分钟"></td>
          <td><button class="btn btn-sm btn-outline-danger kv-remove"><i class="fa fa-trash"></i></button></td>
        </tr>`;
    }
    if (kind === 'body') {
      return `
        <tr>
          <td><input class="form-control form-control-sm kv-key" placeholder="Key"></td>
          <td>
            <div class="input-group input-group-sm body-value-group">
              <input class="form-control form-control-sm kv-value" placeholder="Value">
              <input class="form-control form-control-sm kv-file d-none" type="file">
            </div>
          </td>
          <td>
            <select class="form-select form-select-sm kv-kind">
              <option value="text">text</option>
              <option value="file">file</option>
            </select>
          </td>
          <td><button class="btn btn-sm btn-outline-danger kv-remove"><i class="fa fa-trash"></i></button></td>
        </tr>`;
    }
    return '';
  }

  function parseByType(val, type){
    if (type === 'integer') {
      const n = parseInt(val, 10);
      return isNaN(n) ? 0 : n;
    }
    if (type === 'number') {
      const n = parseFloat(val);
      return isNaN(n) ? 0 : n;
    }
    if (type === 'boolean') {
      if (typeof val === 'boolean') return val;
      const s = String(val).trim().toLowerCase();
      return s === 'true' || s === '1' || s === 'yes';
    }
    if (type === 'array') {
      if (Array.isArray(val)) return val;
      return String(val).split(',').map(s => s.trim()).filter(Boolean);
    }
    return val; // string
  }

  function buildQuery(params){
    const parts = [];
    params.forEach(({key, value, type}) => {
      if (!key) return;
      const parsed = parseByType(value, type||'string');
      if (Array.isArray(parsed)) {
        parsed.forEach(v => parts.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(v))}`));
      } else {
        parts.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(parsed))}`);
      }
    });
    return parts.length ? `?${parts.join('&')}` : '';
  }

  function collectRows($tbody, kind){
    const rows = [];
    $tbody.find('tr').each(function(){
      const $tr = $(this);
      const key = $tr.find('.kv-key').val().trim();
      if (!key) return;
      const row = { key };
      if (kind === 'params') {
        row.value = $tr.find('.kv-value').val();
        row.type = $tr.find('.kv-type').val();
      } else if (kind === 'headers') {
        row.value = $tr.find('.kv-value').val();
      } else if (kind === 'cookies') {
        row.value = $tr.find('.kv-value').val();
        const ex = parseInt($tr.find('.kv-expire').val(), 10);
        row.expire = isNaN(ex) ? null : ex;
      } else if (kind === 'body') {
        const kindVal = $tr.find('.kv-kind').val();
        row.kind = kindVal;
        if (kindVal === 'file') {
          const fileInput = $tr.find('.kv-file')[0];
          row.file = fileInput && fileInput.files && fileInput.files[0] ? fileInput.files[0] : null;
        } else {
          row.value = $tr.find('.kv-value').val();
        }
      }
      rows.push(row);
    });
    return rows;
  }

  function setCookie(name, value, minutes){
    const expires = minutes ? new Date(Date.now() + minutes*60000).toUTCString() : '';
    let cookieStr = `${encodeURIComponent(name)}=${encodeURIComponent(value)}; path=/`;
    if (expires) cookieStr += `; expires=${expires}`;
    document.cookie = cookieStr;
  }

  function currentCookieString(){
    return document.cookie || '';
  }

  function tryInitAceEditor(){
    const el = document.getElementById('apitest-json-editor');
    if (!el) return null;
    if (window.ace && typeof window.ace.edit === 'function') {
      const editor = window.ace.edit(el);
      editor.setTheme('ace/theme/github');
      editor.session.setMode('ace/mode/json');
      editor.session.setUseWorker(false);
      editor.setValue(`{
  "hello": "world"
}`, -1);
      return editor;
    }
    return null;
  }

  function formatHeaders(headers){
    const lines = [];
    for (const k in headers) {
      if (Object.prototype.hasOwnProperty.call(headers, k)) {
        lines.push(`${k}: ${headers[k]}`);
      }
    }
    return lines.join('\n');
  }

  function renderResponse(resp){
    const $bodyPre = $('#resp-body-pre');
    const $headersPre = $('#resp-headers-pre');
    const $info = $('#resp-info-box');
    $('#resp-status').text(resp.status || '-');
    $('#resp-time').text(resp.time || '-');
    $('#resp-size').text(resp.size || '-');
    $headersPre.text(resp.headersText || '');
    $info.html(`
      <div>Final URL: <code>${resp.finalUrl||''}</code></div>
      <div>Content-Type: <code>${resp.contentType||''}</code></div>
    `);
    $bodyPre.text(resp.bodyText||'');
  }

  function safeJsonParse(str){
    try { return JSON.parse(str); } catch(_) { return null; }
  }

  function prettifyBody(bodyText, contentType){
    if (!bodyText) return bodyText;
    const ct = String(contentType||'').toLowerCase();
    if (ct.includes('application/json')) {
      const obj = safeJsonParse(bodyText);
      if (obj) return JSON.stringify(obj, null, 2);
    }
    return bodyText;
  }

  function initApiTest(){
    // 初始化行添加按钮
    $('#add-param-row').on('click', function(){ $('#params-rows').append(kvRowTemplate('params')); });
    $('#add-header-row').on('click', function(){ $('#headers-rows').append(kvRowTemplate('headers')); });
    $('#add-cookie-row').on('click', function(){ $('#cookies-rows').append(kvRowTemplate('cookies')); });
    $('#add-body-kv-row').on('click', function(){ $('#body-kv-rows').append(kvRowTemplate('body')); });
    // 默认加一行
    $('#add-param-row').click();
    $('#add-header-row').click();

    // 删除行事件委托
    $(document).on('click', '.kv-remove', function(){ $(this).closest('tr').remove(); });
    // 切换 body 类型
    $('#body-type').on('change', function(){
      const t = $(this).val();
      $('#json-editor-wrapper').toggleClass('d-none', t!=='json');
      $('#kv-body-wrapper').toggleClass('d-none', !(t==='form-data' || t==='x-www-form-urlencoded'));
      $('#raw-body-wrapper').toggleClass('d-none', !(t==='raw' || t==='xml'));
      $('#binary-body-wrapper').toggleClass('d-none', t!=='binary');
      // 自动填充 Content-Type
      const $ct = $('#body-content-type');
      const defaults = {
        'json': 'application/json',
        'xml': 'application/xml',
        'x-www-form-urlencoded': 'application/x-www-form-urlencoded',
        'raw': 'text/plain',
        'binary': 'application/octet-stream'
      };
      $ct.val(defaults[t]||'');
    });
    $('#body-type').val('none').trigger('change');

    // 初始化 Ace
    const aceEditor = tryInitAceEditor();
    if (!aceEditor) {
      $('#apitest-json-editor').addClass('d-none');
      $('#apitest-json-textarea').removeClass('d-none');
    }

    // 切换 body 行类型（text/file）
    $(document).on('change', '.kv-kind', function(){
      const $tr = $(this).closest('tr');
      const kind = $(this).val();
      $tr.find('.kv-value').toggleClass('d-none', kind==='file');
      $tr.find('.kv-file').toggleClass('d-none', kind!=='file');
    });

    // 手动切换内层选项卡（防止某些环境下Bootstrap未正确激活）
    $(document).on('click', '#api-config-tabs .nav-link', function(){
      const target = $(this).attr('data-bs-target');
      if (!target) return;
      $('#api-config-tabs .nav-link').removeClass('active');
      $(this).addClass('active');
      $('#api-config-content .tab-pane').removeClass('active show');
      $(target).addClass('active show');
    });

    // 发送请求
    $('#api-send').on('click', async function(){
      const start = performance.now();
      const method = $('#api-method').val();
      const urlInput = $('#api-url').val().trim();
      if (!urlInput) { alert('请输入请求URL'); return; }

      // Params
      const params = collectRows($('#params-rows'), 'params');
      const query = buildQuery(params);
      const finalUrl = urlInput + query;

      // Headers
      const headersArr = collectRows($('#headers-rows'), 'headers');
      const headers = {};
      headersArr.forEach(h => { if (h.key) headers[h.key] = h.value || ''; });
      // 浏览器限制：移除 Cookie 头
      if (headers['Cookie']) delete headers['Cookie'];

      // Cookies（当前域）
      const cookiesArr = collectRows($('#cookies-rows'), 'cookies');
      cookiesArr.forEach(c => { if (c.key) setCookie(c.key, c.value||'', c.expire||null); });
      $('#cookies-string').text(currentCookieString());

      // Body
      const bodyType = $('#body-type').val();
      const contentTypeInput = $('#body-content-type').val().trim();
      let ajaxOptions = {
        url: finalUrl,
        method: method,
        headers: headers,
        xhrFields: { withCredentials: $('#api-with-credentials')[0].checked },
        processData: true,
        data: undefined,
        contentType: undefined
      };

      if (method === 'GET' || method === 'HEAD' || bodyType === 'none') {
        // 无 body
      } else if (bodyType === 'json') {
        const jsonStr = aceEditor ? aceEditor.getValue() : $('#apitest-json-textarea').val();
        ajaxOptions.data = jsonStr;
        ajaxOptions.contentType = contentTypeInput || 'application/json';
        ajaxOptions.processData = false;
      } else if (bodyType === 'xml' || bodyType === 'raw') {
        ajaxOptions.data = $('#raw-body-text').val();
        ajaxOptions.contentType = contentTypeInput || (bodyType==='xml' ? 'application/xml' : 'text/plain');
        ajaxOptions.processData = false;
      } else if (bodyType === 'x-www-form-urlencoded') {
        const kv = collectRows($('#body-kv-rows'), 'body');
        const dataObj = {};
        kv.forEach(it => { if (it.kind === 'text' && it.key) dataObj[it.key] = it.value||''; });
        ajaxOptions.data = $.param(dataObj);
        ajaxOptions.contentType = contentTypeInput || 'application/x-www-form-urlencoded; charset=UTF-8';
        ajaxOptions.processData = false; // 已使用 $.param
      } else if (bodyType === 'form-data') {
        const kv = collectRows($('#body-kv-rows'), 'body');
        const fd = new FormData();
        kv.forEach(it => {
          if (!it.key) return;
          if (it.kind === 'file') {
            if (it.file) fd.append(it.key, it.file);
          } else {
            fd.append(it.key, it.value||'');
          }
        });
        ajaxOptions.data = fd;
        ajaxOptions.contentType = false;
        ajaxOptions.processData = false;
      } else if (bodyType === 'binary') {
        const file = $('#binary-body-file')[0].files[0];
        if (!file) { alert('请选择要上传的文件'); return; }
        ajaxOptions.data = file;
        ajaxOptions.contentType = contentTypeInput || 'application/octet-stream';
        ajaxOptions.processData = false;
      }

      // 执行请求
      try {
        const jqxhr = $.ajax(ajaxOptions);
        jqxhr.done(function(data, textStatus, xhr){
          const time = Math.round(performance.now() - start);
          const ct = xhr.getResponseHeader('Content-Type') || '';
          let bodyText = (typeof data === 'string') ? data : JSON.stringify(data);
          bodyText = prettifyBody(bodyText, ct);
          let size = bodyText ? bodyText.length : 0;
          const headersText = xhr.getAllResponseHeaders();
          renderResponse({ status: xhr.status, time, size, headersText, bodyText, finalUrl: xhr.responseURL||ajaxOptions.url, contentType: ct });
        }).fail(function(xhr){
          const time = Math.round(performance.now() - start);
          const ct = xhr.getResponseHeader ? (xhr.getResponseHeader('Content-Type')||'') : '';
          const headersText = xhr.getAllResponseHeaders ? xhr.getAllResponseHeaders() : '';
          const bodyText = xhr.responseText || (xhr.statusText || '请求失败');
          renderResponse({ status: xhr.status, time, size: (bodyText?bodyText.length:0), headersText, bodyText: prettifyBody(bodyText, ct), finalUrl: ajaxOptions.url, contentType: ct });
        });
      } catch (err) {
        renderResponse({ status: '-', time: '-', size: '-', headersText: '', bodyText: String(err), finalUrl: ajaxOptions.url, contentType: '' });
      }
    });

    // 保存接口定义
    $('#api-save-def').on('click', async function(){
      try {
        const def = collectDefinition();
        if (!def.name) { alert('请输入接口名称'); return; }
        if (!def.url) { alert('请输入URL'); return; }
        if (currentEndpointId) {
          await updateApiEndpoint(currentEndpointId, def);
          alert('更新成功');
        } else {
          const resp = await createApiEndpoint(def);
          const id = resp && resp.id ? resp.id : null;
          if (id) currentEndpointId = id;
          alert('创建成功');
        }
        await renderApiTree($('#api-tree-search').val().trim());
      } catch (e) {
        alert('保存失败: ' + (e.responseText || e.statusText || String(e)));
      }
    });

    // 新增接口（清空当前表单）
    $('#api-new-endpoint').on('click', function(){
      currentEndpointId = null;
      $('#api-name').val('');
      $('#api-method').val('GET');
      $('#api-url').val('');
      $('#headers-rows').empty(); $('#params-rows').empty(); $('#body-kv-rows').empty();
      $('#add-header-row').click(); $('#add-param-row').click();
      $('#body-type').val('none').trigger('change');
      $('#raw-body-text').val(''); $('#body-content-type').val('');
    });

    // 树搜索
    $('#api-tree-search').on('input', function(){ renderApiTree($(this).val().trim()); });

    // 树交互（委托）
    $(document).on('click', '.apitest-tree-item', async function(){
      const id = $(this).data('id');
      const detail = await fetchApiDetail(id);
      fillDetail(detail);
    });
    $(document).on('click', '.apitest-tree-del', async function(){
      const id = $(this).data('id');
      if (!confirm('确定删除该接口吗?')) return;
      try { await deleteApiEndpoint(id); await renderApiTree($('#api-tree-search').val().trim()); if (currentEndpointId===id) $('#api-new-endpoint').click(); }
      catch(e){ alert('删除失败: ' + (e.responseText || e.statusText || String(e))); }
    });

    // 初次渲染树
    renderApiTree('');
  }

  // 导出初始化
  window.initApiTest = initApiTest;
})();